﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace Passwords.Controls
{
    public class FocusContainer : DependencyObject
    {



        public static bool GetCanUnfocus(DependencyObject obj)
        {
            return (bool)obj.GetValue(CanUnfocusProperty);
        }

        public static void SetCanUnfocus(DependencyObject obj, bool value)
        {
            obj.SetValue(CanUnfocusProperty, value);
        }

        public static readonly DependencyProperty CanUnfocusProperty =
            DependencyProperty.RegisterAttached("CanUnfocus", typeof(bool), typeof(FocusContainer), new UIPropertyMetadata(false));




        public static TimeSpan GetUnfocusDelay(DependencyObject obj)
        {
            return (TimeSpan)obj.GetValue(UnfocusDelayProperty);
        }

        public static void SetUnfocusDelay(DependencyObject obj, TimeSpan value)
        {
            obj.SetValue(UnfocusDelayProperty, value);
        }

        public static readonly DependencyProperty UnfocusDelayProperty =
            DependencyProperty.RegisterAttached("UnfocusDelay", typeof(TimeSpan), typeof(FocusContainer), new UIPropertyMetadata(null));



        /// <summary>
        /// Get the time span to expire before a control get focused when FocusContainer.IsFocused is set to true.
        /// If set to null, the focus is set immediately.
        /// </summary>
        public static TimeSpan GetFocusDelay(DependencyObject obj)
        {
            return (TimeSpan)obj.GetValue(FocusDelayProperty);
        }

        /// <summary>
        /// Set the time span to expire before a control get focused when FocusContainer.IsFocused is set to true.
        /// If set to null, the focus is set immediately.
        /// </summary>
        public static void SetFocusDelay(DependencyObject obj, TimeSpan value)
        {
            obj.SetValue(FocusDelayProperty, value);
        }

        public static readonly DependencyProperty FocusDelayProperty =
            DependencyProperty.RegisterAttached("FocusDelay", typeof(TimeSpan), typeof(FocusContainer), new UIPropertyMetadata(null));


        public static bool GetIsFocused(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsFocusedProperty);
        }

        /// <summary>
        /// Sets the Focus() of a given element.
        /// </summary>
        /// <param name="obj">Element for which to set the focus.</param>
        /// <param name="value">True to set focus, otherwise false.</param>
        public static void SetIsFocused(DependencyObject obj, bool value)
        {
            obj.SetValue(IsFocusedProperty, value);
        }

        public static readonly DependencyProperty IsFocusedProperty =
             DependencyProperty.RegisterAttached("IsFocused", typeof(bool), typeof(FocusContainer), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnFocusChanged));
        //DependencyProperty.RegisterAttached("IsFocused", typeof(bool), typeof(FocusContainer), new UIPropertyMetadata(false, OnFocusChanged));

        private static void OnFocusChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            bool value = (bool)e.NewValue;
            FrameworkElement element = o as FrameworkElement;
            if (value || GetCanUnfocus(o) == true)
            {
                if (element != null && element.Focusable)
                {
                    TimeSpan duration = value ? GetFocusDelay(o) : GetUnfocusDelay(o);
                    if (duration != null)
                    {
                        DispatcherTimer timer = new DispatcherTimer();
                        timer.Tick += (EventHandler)delegate
                        {
                            timer.Stop();
                            timer.IsEnabled = false;

                            element = GetSelectorItem(element);
                            if (value)
                            {
                                element.Focus();
                            }
                            else
                            {
                                FrameworkElement next = element.PredictFocus(FocusNavigationDirection.Up | FocusNavigationDirection.Down | FocusNavigationDirection.Left | FocusNavigationDirection.Right) as FrameworkElement;
                                if (next != null) next.Focus();
                            }
                        };
                        timer.Interval = duration;
                        timer.IsEnabled = true;
                    }
                    else
                    {
                        element.Dispatcher.BeginInvoke((Action)delegate
                        {
                            element = GetSelectorItem(element);
                            if (value)
                            {
                                element.Focus();
                            }
                            else
                            {
                                FrameworkElement next = element.PredictFocus(FocusNavigationDirection.Up | FocusNavigationDirection.Down | FocusNavigationDirection.Left | FocusNavigationDirection.Right) as FrameworkElement;
                                if (next != null) next.Focus();
                            }
                        });
                    }
                }
            }
        }

        private static FrameworkElement GetSelectorItem(FrameworkElement element)
        {
            Selector selector = element as Selector;
            if (selector != null)
            {
                FrameworkElement selected = selector.SelectedIndex < 0 ? null : selector.ItemContainerGenerator.ContainerFromIndex(selector.SelectedIndex) as FrameworkElement;
                if (selected != null) element = selected;
            }
            return element;
        }
    }
}
